﻿var utils = (function () {

    function theSame(a, b) {
        if (a == null && b == null) return true;
        if (a == null && b != null) return false;
        if (b == null && a != null) return false;
        if (typeof (a) != typeof (b)) return false;
        var type = typeof (a);
        if (type == 'string') return a == b;
        if (a == b) return true;

        if ($.isArray(a) && $.isArray(b)) {
            if (a.length != b.length) return false;
            for (var i = 0; i < a.length; i++) {
                var aVal = a[i];
                var bVal = b[i];
                if (!theSame(aVal, bVal)) return false;
            }
        }

        for (var key in a) {
            var aVal = a[key];
            var bVal = b[key];
            if (!theSame(aVal, bVal)) return false;
        }
        for (var key in b) {
            var aVal = a[key];
            var bVal = b[key];
            if (!theSame(aVal, bVal)) return false;
        }
        return true;
    }

    return {
        async: function (handler) {
            setTimeout(handler, 1);
        },
        isAlpha: function (val) {
            if (val >= 'A' && val <= 'Z') return true;
            if (val >= 'a' && val <= 'z') return true;
            return false;
        },
        isDigit: function (val) {
            if (val >= '0' && val <= '9') return true;
            return false;
        },
        isNullOrWhiteSpace: function (val) {
            if (val === undefined) {
                return true;
            }
            if (val === null) {
                return true;
            }
            if (val === "") {
                return true;
            }
            return false;
        },
        parseInt: function (val) {
            //ecma's parseInt parses '123abc' as 123 and 01234 as hex string.
            val = val.trim();
            if (val.length == 0) return NaN;
            var res = 0;
            for (var i = 0; i < val.length; i++) {
                var ch = val.charAt(i);
                if (ch < '0' || ch > '9') return NaN;
                res = res * 10 + (ch - '0');
            }
            return res;
        },
        generateId: (function () {
            var seed = 0;

            return function () {
                seed++;
                return seed;
            };
        })(),
        theSame: theSame
    };
})();


$.fn.progressZone = function () {

    function init($this) {

        var opened = 0;

        var $progress;

        return {
            enter: function () {
                if (opened == 0) {
                    $progress = $('<div class="loading" />');
                    $this.append($progress);

                }
                opened++;
            },
            set: function (val) {
                if (val < 0) val = 0;
                while (opened < val) {
                    this.enter();
                }
                while (val < opened) {
                    this.leave();
                }
            },
            leave: function () {
                if (opened > 0) {
                    opened--;
                }
                if (opened == 0 && $progress) {
                    $progress.remove();
                    $progress = null;
                }
            }
        };
    }

    var items = [];

    this.each(function () {
        var $item = $(this);

        var data = $item.data('progressZone');
        if (!data) {
            data = init($item);
            $item.data('progressZone', data);
        }
        items.push(data);
    });

    return {
        enter: function () {
            for (var i = 0; i < items.length; i++) {
                items[i].enter();
            }
        },
        set: function (val) {
            for (var i = 0; i < items.length; i++) {
                items[i].set(val);
            }
        },
        leave: function () {
            for (var i = 0; i < items.length; i++) {
                items[i].leave();
            }
        }
    };

};

(function () {

    function refresh(element, spinner) {
        var $progressZone = $(element).progressZone();
        $progressZone.set(spinner.loading());
    }

    ko.bindingHandlers.spinner = {
        update: function (element, valueAccessor) {
            var spinner = ko.utils.unwrapObservable(valueAccessor());

            function onValueChanged() {
                refresh(element, spinner);
            }

            //TODO: unsubscribe form prevoius spinner model.. rare case actally
            spinner.addChanged(onValueChanged);

            refresh(element, spinner);
        }
    };
})();

function SpinnerModel() {

    var dispatcher = $({});

    var value = 0;

    var self = this;

    this.loading = function () {
        if (arguments.length == 0) return value;
        var newVal = arguments[0];
        if (value != newVal) {
            value = newVal;
            dispatcher.trigger('changed');
        }
        return value;
    };

    this.enter = function () {
        self.loading(self.loading() + 1);
    };

    this.leave = function () {
        self.loading(self.loading() - 1);
    };

    this.addChanged = function (handler) {
        dispatcher.bind('changed', handler);
    };

}

ko.bindingHandlers.href = {
    init: function (element, valueAccessor) {
        var value = ko.utils.unwrapObservable(valueAccessor());
        $(element).attr('href', value);
    },
    update: function (element, valueAccessor) {
        var value = ko.utils.unwrapObservable(valueAccessor());
        $(element).attr('href', value);
    }
};

function Base64() {

    var self = this;

    var keyStr = "ABCDEFGHIJKLMNOP" +
             "QRSTUVWXYZabcdef" +
             "ghijklmnopqrstuv" +
             "wxyz0123456789+/" +
             "=";

    var base64Test = /[^A-Za-z0-9\+\/\=]/g;

    function validate(input) {
        if (base64Test.exec(input)) {
            return false;
        }
        return true;
    }

    function encode(array) {
        var output = "";
        var chr1, chr2, chr3 = "";
        var enc1, enc2, enc3, enc4 = "";
        var i = 0;

        do {
            chr1 = array[i++];
            chr2 = array[i++];
            chr3 = array[i++];

            enc1 = chr1 >> 2;
            enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
            enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
            enc4 = chr3 & 63;

            if (isNaN(chr2)) {
                enc3 = enc4 = 64;
            } else if (isNaN(chr3)) {
                enc4 = 64;
            }

            output = output +
               keyStr.charAt(enc1) +
               keyStr.charAt(enc2) +
               keyStr.charAt(enc3) +
               keyStr.charAt(enc4);
        } while (i < array.length);

        return output;
    }

    function decode(input) {
        var output = [];
        var chr1, chr2, chr3 = "";
        var enc1, enc2, enc3, enc4 = "";
        var i = 0;

        input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");

        do {
            enc1 = keyStr.indexOf(input.charAt(i++));
            enc2 = keyStr.indexOf(input.charAt(i++));
            enc3 = keyStr.indexOf(input.charAt(i++));
            enc4 = keyStr.indexOf(input.charAt(i++));

            chr1 = (enc1 << 2) | (enc2 >> 4);
            chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
            chr3 = ((enc3 & 3) << 6) | enc4;

            output.push(chr1);

            if (enc3 != 64) {
                output.push(chr2);
            }
            if (enc4 != 64) {
                output.push(chr3);
            }

        } while (i < input.length);

        return output;
    }

    self.encode = encode;

    self.decode = decode;

    self.validate = validate;

}

function UTF8() {

    var self = this;

    function getBytes(str) {
        if (!str) return [];
        var len = str.length;
        var res = new Array(len);
        for (var i = 0; i < len; i++)
            res[i] = str.charCodeAt(i);
        return res;
    }

    function getString(array) {
        var res = '';
        for (var i = 0; i < array.length; i++) {
            var bt = array[i];
            res += String.fromCharCode(bt);
        }
        return res;
    }

    self.getBytes = getBytes;

    self.getString = getString;
}

ko.bindingHandlers.enterPressed = {
    init: function (element, valueAccessor) {
        var callback = valueAccessor();
        $(element).bind('keypress', function (e) {
            if (e.which == 13) {
                setTimeout(function () {
                    if (callback) callback();
                }, 5);
            }
        });
    }
};

function PagerModel(itemsAccessor) {

    var pageNumber = ko.observable(1);

    var pageSize = ko.observable(10);

    var pagesCount = ko.computed(function () {
        var ps = pageSize();
        var count = itemsAccessor().length;
        return Math.floor((count + ps - 1) / ps);
    });

    var itemsCount = ko.computed(function () {
        var all = itemsAccessor();
        return all.length;
    });

    var items = ko.computed(function () {
        var pn = pageNumber();
        var ps = pageSize();
        var start = (pn - 1) * ps;
        var end = start + ps;

        var all = itemsAccessor();
        start = Math.min(start, all.length);
        end = Math.min(end, all.length);

        var res = [];
        for (var i = start; i < end; i++) {
            var item = all[i];
            res.push(item);
        }
        return res;
    });

    var neighbourPages = ko.computed(function () {
        var res = [];
        var current = pageNumber();
        var count = pagesCount();

        res.push(current);

        var resCount = 7;
        for (var i = 0; (i < resCount * 2) && res.length < resCount; i++) {
            var page = current;
            if (i % 2 == 0) {
                page += 1 + i / 2;
            } else {
                page -= 1 + (i - 1) / 2;
            }
            if (page >= 1 && page <= count) {
                if (page > current) {
                    res.push(page);
                } else {
                    res.unshift(page);
                }
            }
        }

        return res;
    });

    var isFirst = ko.computed(function () {
        return pageNumber() <= 1;
    });

    var isLast = ko.computed(function () {
        return pageNumber() >= pagesCount();
    });

    function goTo(page) {
        pageNumber(page);
    }

    function goToFirst() {
        pageNumber(1);
    }

    function goToLast() {
        pageNumber(pagesCount());
    }

    itemsAccessor.subscribe(goToFirst);

    this.pageNumber = pageNumber;
    this.pagesCount = pagesCount;
    this.pageSize = pageSize;
    this.items = items;
    this.itemsCount = itemsCount;
    this.neighbourPages = neighbourPages;
    this.goTo = goTo;
    this.goToFirst = goToFirst;
    this.goToLast = goToLast;
    this.isFirst = isFirst;
    this.isLast = isLast;
}


function PagerCountModel(urlBuilder) {

    var pageNumber = ko.observable(1);

    var pageSize = ko.observable(10);

    var itemsCount = ko.observable(0);

    var pagesCount = ko.computed(function () {
        var ps = pageSize();
        var count = itemsCount();
        return Math.floor((count + ps - 1) / ps);
    });

    var neighbourPages = ko.computed(function () {
        var res = [];
        var current = pageNumber();
        var count = pagesCount();

        res.push(current);

        var resCount = 7;
        for (var i = 0; (i < resCount * 2) && res.length < resCount; i++) {
            var page = current;
            if (i % 2 == 0) {
                page += 1 + i / 2;
            } else {
                page -= 1 + (i - 1) / 2;
            }
            if (page >= 1 && page <= count) {
                if (page > current) {
                    res.push(page);
                } else {
                    res.unshift(page);
                }
            }
        }

        return res;
    });

    function getLinkTo(page) {
        if (urlBuilder) {
            return urlBuilder(page);
        }
        return '';
    }
    
    var isFirst = ko.computed(function () {
        return pageNumber() <= 1;
    });

    var isLast = ko.computed(function () {
        return pageNumber() >= pagesCount();
    });

    function goTo(page) {
        pageNumber(page);
    }

    function goToFirst() {
        pageNumber(1);
    }

    function goToLast() {
        pageNumber(pagesCount());
    }

    this.pageNumber = pageNumber;
    this.pagesCount = pagesCount;
    this.pageSize = pageSize;
    this.itemsCount = itemsCount;
    this.neighbourPages = neighbourPages;
    this.goTo = goTo;
    this.goToFirst = goToFirst;
    this.goToLast = goToLast;
    this.isFirst = isFirst;
    this.isLast = isLast;
    this.getLinkTo = getLinkTo;
}

$.fn.cartIcon = function () {

    function init($this) {

        var $cnt = $this.find('.count');

        var value = 0;

        function updateText(cnt) {
            if (cnt > 0) {
                $cnt.text(cnt);
            } else {
                $cnt.text('');
            }
        }

        function setItemsCount(val) {
            if (val != value) {
                $cnt.stop();
                if (val > value) {
                    value = val;
                    $cnt.animate({
                        fontSize: '120%'
                    }, {
                        complete: function () {
                            $cnt.animate({
                                fontSize: '100%'
                            });
                        }
                    });
                } else if (val < value) {
                    value = val;
                }
                updateText(value);
            }
        }

        return {
            setItemsCount: setItemsCount
        };
    }

    var data = this.data('cartIcon');
    if (!data) {
        data = init(this);
        this.data('cartIcon', data);
    }
    return data;
};

ko.bindingHandlers.cartIcon = (function () {


    function init(element, valueAccessor) {
        var val = ko.utils.unwrapObservable(valueAccessor());
        $(element).cartIcon().setItemsCount(val);
    }

    function update(element, valueAccessor) {
        var val = ko.utils.unwrapObservable(valueAccessor());
        $(element).cartIcon().setItemsCount(val);
    }

    return {
        init: init,
        update: update
    };
})();

ko.bindingHandlers.enterPressed = {
    init: function (element, valueAccessor) {
        var callback = valueAccessor();
        $(element).bind('keypress', function (e) {
            if (e.which == 13) {
                setTimeout(function () {
                    if (callback) callback();
                }, 5);
            }
        });
    }
};

$.fn.rating = function () {

    function init($this) {

        var dispatcher = $({});

        var max = 5;
        var val = 0;
        var readonly = false;

        var iconSize = 24;

        var $stars = $('<div class="stars"></div>');
        $this.append($stars);

        var stars = [];

        function createStar(index) {
            var $star = $('<a href="javascript: void(0)" class="star"></a>');
            $star.bind('click', function () {
                if (!readonly) {
                    setValue(index + 1);
                }
            });
            return $star;
        }

        function createStars() {
            stars = [];
            $stars.empty();
            for (var i = 0; i < max; i++) {
                var star = createStar(i);
                $stars.append(star);
                stars.push(star);
            }
        }

        createStars();


        function setSpritePos($star, iconIndex) {
            $star.css({ 'background-position': '0 ' + (-iconSize * iconIndex) + 'px' });
        }

        function getValue() {
            return val;
        }

        function setValue(rating) {
            if (val != rating) {
                val = rating;

                for (var i = 0; i < stars.length; i++) {
                    var $star = stars[i];
                    var sprites = 21;
                    var starMin = i;
                    var delta = 100 * (rating - starMin);
                    if (delta >= 100) {
                        setSpritePos($star, sprites - 1);
                    } else if (delta <= 0) {
                        setSpritePos($star, 0);
                    } else {
                        var percentagePerSprite = 100 / (sprites - 1);
                        var index = Math.round(delta / percentagePerSprite);
                        setSpritePos($star, index);
                    }
                }

                dispatcher.trigger('change', val);
            }
        }

        function setReadonly(ro) {
            readonly = ro;
            if (ro) {
                $this.addClass('readonly');
            } else {
                $this.removeClass('readonly');
            }
        }

        function addOnChange(handler) {
            dispatcher.bind('change', handler);
        }

        return {
            getValue: getValue,
            setValue: setValue,
            setReadonly: setReadonly,
            addOnChange: addOnChange
        };
    }

    var data = this.data('rating');
    if (!data) {
        data = init(this);
        this.data('rating', data);
    }
    return data;

};

function RatingModel() {

    var self = this;

    var max = ko.observable(5);

    var value = ko.observable(0);

    self.max = max;
    self.value = value;
}

ko.bindingHandlers.rating = (function () {

    return {
        init: function (element, valueAccessor) {
            var model = ko.utils.unwrapObservable(valueAccessor());
            var $rating = $(element).rating();
            $rating.setValue(model.value());
            $rating.addOnChange(function (ev, newVal) {
                model.value(newVal);
            });
        },
        update: function (element, valueAccessor) {
            var model = ko.utils.unwrapObservable(valueAccessor());
            $(element).rating().setValue(model.value());
        }
    };
})();

ko.bindingHandlers.ratingValue = (function () {

    return {
        init: function (element, valueAccessor) {
            var model = ko.utils.unwrapObservable(valueAccessor());
            var $rating = $(element).rating();
            $rating.setValue(model);
            $rating.addOnChange(function (ev, newVal) {
                valueAccessor()(newVal);
            });
        },
        update: function (element, valueAccessor) {
            var model = ko.utils.unwrapObservable(valueAccessor());
            var $rating = $(element).rating();
            $rating.setValue(model);
        }
    };
})();

ko.bindingHandlers.ratingReadonly = (function () {

    return {
        init: function (element, valueAccessor) {
            var model = ko.utils.unwrapObservable(valueAccessor());
            var $rating = $(element).rating();
            $rating.setReadonly(model);
        },
        update: function (element, valueAccessor) {
            var model = ko.utils.unwrapObservable(valueAccessor());
            var $rating = $(element).rating();
            $rating.setReadonly(model);
        }
    };
})();


ko.bindingHandlers.integerValue = {
    init: function (element, valueAccessor) {
        var $element = $(element);
        $(element).bind('change', function () {
            var val = $element.val();
            var intVal = utils.parseInt(val);
            if (isNaN(intVal)) {
                valueAccessor()(val);
            } else {
                valueAccessor()(intVal);
            }
        });
    },
    update: function (element, valueAccessor) {
        var value = ko.utils.unwrapObservable(valueAccessor());
        $(element).val(value);
    }
};
